package cn.chiship.framework.docs.biz.controller;

import cn.chiship.framework.docs.biz.entity.FileResources;
import cn.chiship.framework.docs.biz.entity.FileResourcesExample;
import cn.chiship.framework.docs.biz.service.FileResourcesService;
import cn.chiship.framework.docs.core.common.DocsCommonConstants;
import cn.chiship.framework.docs.core.properties.FileUploadProperties;
import cn.chiship.sdk.core.annotation.NoParamsSign;
import cn.chiship.sdk.core.annotation.NoVerificationAppIdAndKey;
import cn.chiship.sdk.core.base.constants.BaseCacheConstants;
import cn.chiship.sdk.core.encryption.ProofUtil;
import cn.chiship.sdk.core.enums.HeaderEnum;
import cn.chiship.sdk.core.exception.custom.BusinessException;
import cn.chiship.sdk.core.util.*;
import cn.chiship.sdk.core.util.http.ResponseUtil;
import cn.chiship.sdk.framework.util.File2Util;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import cn.chiship.sdk.cache.service.RedisService;
import cn.chiship.sdk.framework.base.BaseController;
import cn.chiship.sdk.core.enums.BaseResultEnum;
import cn.chiship.sdk.framework.base.BaseService;
import cn.chiship.framework.common.constants.CommonCacheConstants;
import cn.chiship.sdk.core.base.BaseResult;
import com.google.common.base.Strings;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.apache.catalina.connector.ClientAbortException;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.List;

/**
 * controller
 *
 * @author lijian
 * @date 2020/4/24
 */
@Controller
@RequestMapping("/fileView")
@NoParamsSign
@Api(tags = "文件预览")
public class FileViewController {

    private static final Logger LOGGER = LoggerFactory.getLogger(FileViewController.class);

    private static final String PROF_APP_SECRET = "appSecret";

    private static final String SIGN_KEY = "signKey";

    private static final String STRING_BYTES = "bytes=";

    private static final String H_BAR = "-";

    private static final int NUMBER_TWO = 2;

    @Resource
    private FileResourcesService fileResourcesService;

    @Resource
    FileUploadProperties fileUploadProperties;

    @Resource
    RedisService redisService;

    @ApiOperation(value = "根据UUID获取文件详情")
    @ApiImplicitParams({@ApiImplicitParam(name = "uuid", value = "文件标识", dataTypeClass = String.class,
            paramType = "path", required = true)})
    @GetMapping(value = "/getByUUID/{uuid}")
    public ResponseEntity<BaseResult> getByUuid(@PathVariable(value = "uuid") String uuid) {
        FileResources fileResources = fileResourcesService.cacheByFileUuid(uuid);
        return new ResponseEntity<>(
                (ObjectUtil.isEmpty(fileResources) ? BaseResult.ok() : BaseResult.ok(fileResources)), HttpStatus.OK);
    }

    @ApiOperation(value = "根据UUID获取文件列表")
    @ApiImplicitParams({@ApiImplicitParam(name = "uuids", value = "文件标识", dataTypeClass = String.class,
            paramType = "query", required = true)})
    @GetMapping(value = "/listByUUID")
    public ResponseEntity<BaseResult> listByUuid(@RequestParam(value = "uuids") String uuids) {
        List<FileResources> fileResources = fileResourcesService.cacheByFileUuid(StringUtil.strToListString(uuids, ","));
        return new ResponseEntity<>(BaseResult.ok(fileResources), HttpStatus.OK);
    }

    @ApiOperation(value = "下载")
    @PostMapping(value = "/download/{uuid}")
    @NoVerificationAppIdAndKey
    public void download(@PathVariable(value = "uuid") String uuid, HttpServletRequest request,
                         HttpServletResponse response) throws Exception {
        view(uuid, true, request, response);
    }

    @ApiOperation(value = "预览")
    @PostMapping(value = "/{uuid}")
    @NoVerificationAppIdAndKey
    public void viewPost(@PathVariable(value = "uuid") String uuid, HttpServletRequest request,
                         HttpServletResponse response) throws Exception {
        view(uuid, false, request, response);
    }

    @ApiOperation(value = "预览")
    @GetMapping(value = "/{uuid}")
    @NoVerificationAppIdAndKey
    public void viewGet(@PathVariable(value = "uuid") String uuid, HttpServletRequest request,
                        HttpServletResponse response) throws Exception {
        view(uuid, false, request, response);
    }

    public void view(String uuid, Boolean download, HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        validateAppIdAndAppKey(request, response);
        FileResources file = fileResourcesService.cacheByFileUuid(uuid);
        if (ObjectUtil.isEmpty(file)) {
            outFileNo(response, true);
            return;
        }

        String fileExt = file.getFileExt();
        if (download) {
            ResponseUtil.setAttachmentResponseHeader(response, file.getOriginalFileName() + "." + file.getFileExt());
        }

        switch (fileExt) {
            case "jpg":
            case "jpeg":
            case "png":
                viewImage(file, request, response);
                break;
            case "mp3":
            case "mp4":
                viewAudio(file, request, response);
                break;
            case "pdf":
                viewPdf(file, request, response);
                break;
            case "xlsx":
            case "xls":
                viewExcel(file, request, response);
                break;
            default:
                downloadFile(file, response);
        }

    }

    public void viewImage(FileResources fileResources, HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        response.setDateHeader("Expires", 0);
        response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
        response.addHeader("Cache-Control", "post-check=0, pre-check=0");
        response.setHeader("Pragma", "no-cache");
        if (DocsCommonConstants.STORAGE_LOCATION_LOCAL == fileResources.getStorageLocation()) {
            String filePath = fileUploadProperties.getUploadFullPath(fileResources.getStorageRelativePath());
            File file = new File(filePath);
            if (!file.exists()) {
                writeError(response, BaseResult.error("您访问的文件资源不存在!"));
                return;
            }
            String contentType = request.getServletContext().getMimeType(file.getName());

            response.setContentType(contentType);
            try {
                FileInputStream fileInputStream = new FileInputStream(file);
                OutputStream outputStream = response.getOutputStream();
                IOUtils.write(IOUtils.toByteArray(fileInputStream), outputStream);
            } catch (Exception e) {

            }
        } else {
            response.setContentType("image/png");
            OutputStream outputStream = response.getOutputStream();
            IOUtils.write(FileUtil.downLoadToByte(fileResources.getStoragePath()), outputStream);
        }

    }

    /**
     * 预览音视频
     *
     * @param fileResources
     * @param request
     * @param response
     * @throws Exception
     */
    public void viewAudio(FileResources fileResources, HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        if (DocsCommonConstants.STORAGE_LOCATION_LOCAL == fileResources.getStorageLocation()) {
            String storageRelativePath = fileResources.getStorageRelativePath();
            String filePath = fileUploadProperties.getUploadFullPath(storageRelativePath);
            File audio = new File(filePath);
            if (!audio.exists()) {
                writeError(response, BaseResult.error("您访问的文件资源不存在!"));
                return;
            }
            String range = request.getHeader("Range");
            /**
             * 开始下载位置
             */
            long startByte = 0;
            /**
             * 结束下载位置
             */
            long endByte = audio.length() - 1;
            /**
             * 有range的话
             */
            if (range != null && range.contains(STRING_BYTES) && range.contains(H_BAR)) {
                range = range.substring(range.lastIndexOf("=") + 1).trim();
                String[] ranges = range.split(H_BAR);
                try {
                    /**
                     * 判断range的类型
                     */
                    if (ranges.length == 1) {
                        /**
                         * 类型一：bytes=-2343
                         */
                        if (range.startsWith(H_BAR)) {
                            endByte = Long.parseLong(ranges[0]);
                        }
                        /**
                         * 类型二：bytes=2343-
                         */
                        else if (range.endsWith(H_BAR)) {
                            startByte = Long.parseLong(ranges[0]);
                        }
                    }
                    /**
                     * 类型三：bytes=22-2343
                     */
                    else if (ranges.length == NUMBER_TWO) {
                        startByte = Long.parseLong(ranges[0]);
                        endByte = Long.parseLong(ranges[1]);
                    }
                } catch (NumberFormatException e) {
                    startByte = 0;
                    endByte = audio.length() - 1;
                }
            }
            /**
             * 要下载的长度
             */
            long contentLength = endByte - startByte + 1;
            /**
             * 文件名
             */
            String fileName = audio.getName();
            /**
             * 文件类型
             */
            String contentType = request.getServletContext().getMimeType(fileName);
            /**
             * 各种响应头设置
             */
            response.setHeader("Accept-Ranges", "bytes");
            response.setStatus(206);
            response.setContentType(contentType);
            response.setHeader("Content-Type", contentType);
            response.setHeader("Content-Length", String.valueOf(contentLength));
            response.setHeader("Content-Range", "bytes " + startByte + "-" + endByte + "/" + audio.length());
            BufferedOutputStream outputStream = null;
            RandomAccessFile randomAccessFile = null;
            /**
             * 已传送数据大小
             */
            long transmitted = 0;
            try {
                randomAccessFile = new RandomAccessFile(audio, "r");
                outputStream = new BufferedOutputStream(response.getOutputStream());
                byte[] buff = new byte[4096];
                int len = 0;
                randomAccessFile.seek(startByte);
                while ((transmitted + len) <= contentLength && (len = randomAccessFile.read(buff)) != -1) {
                    outputStream.write(buff, 0, len);
                    transmitted += len;
                }
                if (transmitted < contentLength) {
                    len = randomAccessFile.read(buff, 0, (int) (contentLength - transmitted));
                    outputStream.write(buff, 0, len);
                    transmitted += len;
                }
                outputStream.flush();
                response.flushBuffer();
                randomAccessFile.close();
                System.out.println("下载完毕：" + startByte + "-" + endByte + "：" + transmitted);
            } catch (ClientAbortException e) {
                System.out.println("用户停止下载：" + startByte + "-" + endByte + "：" + transmitted);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (randomAccessFile != null) {
                        randomAccessFile.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        } else {
            writeError(response, BaseResult.error("待接入!"));
        }
    }

    public void viewPdf(FileResources fileResources, HttpServletRequest request, HttpServletResponse response)
            throws Exception {

        if (DocsCommonConstants.STORAGE_LOCATION_LOCAL == fileResources.getStorageLocation()) {
            String filePath = fileUploadProperties.getUploadFullPath(fileResources.getStorageRelativePath());
            File file = new File(filePath);
            if (!file.exists()) {
                writeError(response, BaseResult.error("您访问的文件资源不存在!"));
                return;
            }
            response.setHeader("Pragma", "no-cache");
            response.setContentType("application/pdf");
            response.setHeader("Expires", "0");
            response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");

            OutputStream os = response.getOutputStream();
            os.write(readStream(new FileInputStream(file)));
            os.flush();
        } else {
            writeError(response, BaseResult.error("待接入!"));
        }

    }

    public void viewExcel(FileResources fileResources, HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        if (DocsCommonConstants.STORAGE_LOCATION_LOCAL == fileResources.getStorageLocation()) {
            String filePath = fileUploadProperties.getUploadFullPath(fileResources.getStorageRelativePath());
            File file = new File(filePath);
            if (!file.exists()) {
                writeError(response, BaseResult.error("您访问的文件资源不存在!"));
                return;
            }
            response.setHeader("content-type", "application/vnd.ms-excel;charset=utf-8");
            response.setContentType("application/vnd.ms-excel;charset=utf-8");
            response.addHeader("Content-Length", String.valueOf(file.length()));
            byte[] buff = new byte[1024];
            BufferedInputStream bis = null;
            OutputStream os;
            try {
                os = response.getOutputStream();
                bis = new BufferedInputStream(new FileInputStream(file));
                int i = bis.read(buff);
                while (i != -1) {
                    os.write(buff, 0, buff.length);
                    os.flush();
                    i = bis.read(buff);
                }
            } catch (Exception e) {
                throw new BusinessException("系统发生错误,或文件不存在!");
            } finally {
                if (bis != null) {
                    try {
                        bis.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }

        } else {
            writeError(response, BaseResult.error("待接入!"));
        }

    }

    public void downloadFile(FileResources fileResources, HttpServletResponse response) throws Exception {
        byte[] buffer;
        if (DocsCommonConstants.STORAGE_LOCATION_LOCAL == fileResources.getStorageLocation()) {
            String storageRelativePath = fileResources.getStorageRelativePath();
            String filePath = fileUploadProperties.getUploadFullPath(storageRelativePath);
            File file = new File(filePath);
            if (!file.exists()) {
                writeError(response, BaseResult.error("您访问的文件资源不存在!"));
                return;
            }
            InputStream fis = new BufferedInputStream(new FileInputStream(file));
            buffer = new byte[fis.available()];
            fis.read(buffer);
        } else {
            buffer = ImageUtil.getImgByteByUrl(fileResources.getStoragePath());

        }
        response.addHeader("Content-Length", "" + fileResources.getFileSize());
        ServletOutputStream out = response.getOutputStream();
        IOUtils.write(buffer, out);
        out.flush();

    }

    /**
     * 资源文件不存在
     *
     * @param response
     */
    public void outFileNo(HttpServletResponse response, boolean isImage) throws Exception {
        response.setDateHeader("Expires", 0);
        response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
        response.addHeader("Cache-Control", "post-check=0, pre-check=0");
        response.setHeader("Pragma", "no-cache");
        response.setContentType("image/jpeg");
        ServletOutputStream out = response.getOutputStream();
        IOUtils.write(File2Util.readResource(isImage ? "noImg.png" : "noFile.png"), out);
        out.flush();
    }

    /**
     * 读取管道中的流数据
     */
    public byte[] readStream(InputStream inStream) {
        ByteArrayOutputStream bops = new ByteArrayOutputStream();
        int data = -1;
        try {
            while ((data = inStream.read()) != -1) {
                bops.write(data);
            }
            return bops.toByteArray();
        } catch (Exception e) {
            return null;
        }
    }

    public void writeError(HttpServletResponse response, BaseResult ucResult) throws Exception {
        response.setContentType("application/json");
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(response.getOutputStream()));
        bufferedWriter.write(JSON.toJSONString(ucResult));
        bufferedWriter.close();
    }

    public void validateAppIdAndAppKey(HttpServletRequest request, HttpServletResponse response) throws Exception {
        String appId = request.getHeader(HeaderEnum.HEADER_APP_ID.getName());
        String appKey = request.getHeader(HeaderEnum.HEADER_APP_KEY.getName());
        if (Strings.isNullOrEmpty(appKey) || Strings.isNullOrEmpty(appId)) {
            appId = request.getParameter("AppId");
            appKey = request.getParameter("AppKey");
        }

        if (Strings.isNullOrEmpty(appKey) || Strings.isNullOrEmpty(appId)) {
            writeError(response, BaseResult.error(BaseResultEnum.HEADER_NO_APP_KEY_AND_APP_ID, null));
            return;
        } else {
            JSONObject prof = (JSONObject) redisService
                    .get(CommonCacheConstants.buildKey(BaseCacheConstants.REDIS_PROOF_PREFIX));
            if (prof != null) {
                if (!ProofUtil.verifyAppSecret(appId, appKey, prof.getString(SIGN_KEY),
                        prof.getString(PROF_APP_SECRET))) {
                    writeError(response, BaseResult.error(BaseResultEnum.ERROR_APP_KEY, null));
                    return;
                }
            } else {
                writeError(response, BaseResult.error(BaseResultEnum.ERROR_APP_ID, null));
                return;
            }
        }
    }

}
